package errcode

import (
	"context"
	"strings"
	"sync/atomic"
)

const (
	quit = 1000000000
)

var Debug2 = false

// ErrCtx 错误处理的上下文
// 保存了错误处理需要的信息
//
// ErrCtx 不能被重用
type ErrCtx struct {
	errchan chan ErrInfo //错误保存
	errnum  int32        //错误计数
	errbol  int32        //是否有错误 1=有 -1=没有
}

// NewErrCtx 创建错误处理的上下文
func NewErrCtx() *ErrCtx {
	return &ErrCtx{
		errchan: make(chan ErrInfo, 100),
		errnum:  0,
		errbol:  -1,
	}
}

// Panic 进行报错
//   - File是文件名称
//   - line是行数
//   - addInfo是附加错误信息,可以为nil
//   - err是错误码，可以有多个
func (e *ErrCtx) Panic(file string, line int, addInfo Msg, err ...ErrCode) {
	atomic.StoreInt32(&e.errbol, 1)
	//错误提示 = 错误码表示的提示信息
	e.errchan <- NewErrInfo(file, line, addInfo, ErrEnumStr(err...))
	atomic.AddInt32(&e.errnum, 1)
}

// ErrEnumStr 实现将错误码 [ErrCode] 转换为提示信息的算法
func ErrEnumStr(code ...ErrCode) string {
	var buf strings.Builder
	for _, v := range code {
		// 从左到右加上每个错误码表示的提示信息
		buf.WriteString(ErrCodeStrMap[v])
		//加一个空格
		buf.WriteString(" ")
	}
	return buf.String()
}

// loadErrContext 读取一个错误,返回错误和剩余错误数
//   - ctx传递取消信号
func (e *ErrCtx) loadErrContext(ctx context.Context) (ErrInfo, int32) {
	select {
	case <-ctx.Done():
		return ErrInfo{}, quit
	case ret := <-e.errchan:
		return ret, atomic.AddInt32(&e.errnum, -1)
	}
}

// Handle 进行错误处理,是阻塞的
//   - Handle循环读取到错误后，调用errfn处理，直到ctx取消并处理完所有错误，如果有错误被保存过，调用retfn
//   - 取消一个没有取消的ctx是唯一从外部结束Handle的方法
//   - ctx传输取消信号
//   - errfn实际处理错误
//   - retfn定义读取到错误且收到取消信号后操作
func (e *ErrCtx) Handle(ctx context.Context, errfn func(ErrInfo), retfn func()) { //错误处理
	for {
		err, num := e.loadErrContext(ctx) //读取一个错
		//获得取消信号
		if num == quit { //主动取消
			break
		}
		//没有取消
		errfn(err)
	}
	for len(e.errchan) > 0 { //如果有没有处理过的错误
		errfn(<-e.errchan)
	}
	if Debug2 {
		retfn()
		return
	}
	if e.Errbol() { //如果有错误被保存过
		retfn()
	}
}

// Errbol 返回是否保存过错误
func (e *ErrCtx) Errbol() bool {
	return atomic.LoadInt32(&e.errbol) != -1
}
